package com.yixing.tech.wms.service.impl;

import com.yixing.tech.common.base.BusinessException;
import com.yixing.tech.common.enums.YesOrNo;
import com.yixing.tech.common.service.AbstractWmsService;
import com.yixing.tech.wms.entity.InStockWork;
import com.yixing.tech.wms.service.*;
import com.yixing.tech.wms.stock.out.StockOutConstants;
import com.yixing.tech.wms.stock.out.callback.error.ErrorCodeEnum;
import com.yixing.tech.wms.stock.out.callback.error.TaskErrorCallbackVO;
import com.yixing.tech.wms.stock.out.domain.Task;
import com.yixing.tech.wms.stock.out.domain.vo.TaskMethod;
import com.yixing.tech.wms.stock.out.domain.vo.TaskType;
import com.yixing.tech.wms.utils.DocumentUtils;
import com.mongodb.client.result.UpdateResult;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.bson.Document;
import org.bson.types.ObjectId;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationOperation;
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
import org.springframework.data.mongodb.core.aggregation.ConvertOperators;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.stream.Collectors;

import static com.yixing.tech.common.CommonConstant.*;
import static com.yixing.tech.common.CommonConstant.InStatus.inStockOK;

/**
 * WcsCallbackService
 *
 * @author yixing tech
 * @since 1.0.0
 */
@Slf4j
@Service
public class WcsWmsCallbackServiceImpl extends AbstractWmsService implements WcsWmsCallbackService {
    @Value("${tenant-id: 6204cb763da841718ddd1cf8}")
    private String tenantId;
    @Autowired
    MongoTemplate mongoTemplate;
    @Autowired
    StockService stockService;
    @Autowired
    RawMaterialInStockService rawMaterialInStockService;
    @Autowired
    ProductInStockService productInStockService;
    @Autowired
    SalesReturnStockService salesReturnStockService;
    @Autowired
    StockLocationService stockLocationService;
    @Autowired
    private WmsTaskService wmsTaskService;

    @Override
    public void handleCallback(String taskCode, String method, String locCode, String podCode) {
        log.info("处理任务回调信息:taskCode[{}],method[{}],locCode[{}],podCode[{}]", taskCode, method, locCode, podCode);

        List<AggregationOperation> operations = new ArrayList<>();
        operations.add(Aggregation.match(Criteria.where("tenantId").is(tenantId).and("isDelete").ne(YesOrNo.YES.ordinal())
                .and("taskCode").is(taskCode)));
        operations.add(Aggregation.addFields()
                .addFieldWithValue("taskType", ConvertOperators.ToString.toString("$taskType.id"))
                .addFieldWithValue("status", ConvertOperators.ToString.toString("$status.id"))
                .addFieldWithValue("performStatus", ConvertOperators.ToString.toString("$performStatus.id"))
                .build());

        AggregationResults<Task> results = mongoTemplate.aggregate(Aggregation.newAggregation(operations), StockOutConstants.TASK_TEMPLATE, Task.class);
        if (ObjectUtils.isEmpty(results.getMappedResults())) {
            return;
        }
        Task task = results.getMappedResults().get(0);
        if (TaskMethod.OUTBIN.getAction().equals(method)) {
            // 处理离开起点
            handleOutBin(taskCode, method, locCode, podCode);
        } else if (TaskType.STOCK_OUT.getId().equals(task.getTaskType()) && TaskMethod.FINISH.getAction().equals(method)) {
            // 容器到达分拣区
            containerArrive(taskCode, method, locCode, podCode, task);
        } else if (TaskType.STOCK_IN.getId().equals(task.getTaskType()) && TaskMethod.FINISH.getAction().equals(method)) {
            // 容器到达仓库
            containerInStock(taskCode, method, locCode, podCode, task);
        }
    }

    @Override
    public Object handleErrorCallback(TaskErrorCallbackVO errorCallbackVO) throws BusinessException {
        ErrorCodeEnum errorCodeEnum = ErrorCodeEnum.getByCode(errorCallbackVO.getErrorCode());
        if(ObjectUtils.isEmpty(errorCodeEnum)){
            log.error("异常码不在预期范围内:errorCode[{}]", errorCallbackVO.getErrorCode());
            throw new BusinessException("异常码不在预期范围内");
        }

        Task task = wmsTaskService.findTaskByTaskCode(errorCallbackVO.getTaskCode());
        if(ObjectUtils.isEmpty(task)) {
            log.error("异常任务信息不存在:taskCode[{}]", errorCallbackVO.getTaskCode());
            throw new BusinessException("异常任务信息不存在");
        }

        Object result = handleCommonError(task);

//        switch (errorCodeEnum) {
//            case DOUBLE_IN:
//                result = doubleIn(task);
//                break;
//            case DOUBLE_IN2:
//                result = doubleIn2(task);
//                break;
//            case FETCHING_NOTHING:
//                result = fetchingNothing(task);
//                break;
//            case FETCHING_NOTHING2:
//                result = fetchingNothing2(task);
//        }
        return result;
    }

    @Transactional(rollbackFor = Exception.class)
    public String handleCommonError(Task task) throws BusinessException {
        List<String> workCodes = task.getOperationNo();
        List<InStockWork> works = mongoTemplate.find(Query.query(newCriteria().and("code").in(workCodes)),
                InStockWork.class, "entity_" + IN_STOCK_TASK);
        InStockWork fw = works.get(0);
        String inStatus = DocumentUtils.unwrapDictItem(fw.getInStatus());
        if(inStatus.equals(InStatus.applyOK.getCode())) {
            // 发生在分拣前取消任务
            return cancelWork(works);
        } else {
            // 发生在分拣后重新查找库位
            return reApplyLocation(works);
        }
    }

    @Transactional(rollbackFor = Exception.class)
    public String reApplyLocation(List<InStockWork> works) throws BusinessException {
        InStockWork fw = works.get(0);
        String materialCode = fw.getMaterialCode();
        Document material = simpleFindOne("entity_" + MATERIAL, "code", materialCode);
        String abc = material.getString("abc");
        Document rule = mongoTemplate.findOne(Query.query(newCriteria().and("materialCode").is(materialCode)), Document.class,
                "entity_" + STOCK_RULE);
        List<String> areas = rule.getList("areas", String.class);
        List<Document> locations = stockLocationService.queryEmptyLocation(areas, PRODUCTION_IN_STOCK_DETAIL.equals(fw.getDtlSourceTable()));
        if(CollectionUtils.isEmpty(locations)) {
            return cancelWork(works);
        } else if (A.equals(abc) && locations.size() <= 5) {
            return cancelWork(works);
        }  else {
            Document fl = locations.get(0);
            String locationCode = fl.getString("code");
            Update update = new Update();
            update.set("locationCode", locationCode);
            update.set("areaCode", fl.getString("area"));
            update.set("updateTime", new Date());
            works.forEach(x -> updateById(x.get_id().toString(), update, "entity_" + IN_STOCK_TASK));
            return locationCode;
        }
    }

    @Transactional(rollbackFor = Exception.class)
    public String cancelWork(List<InStockWork> works) {
        // 删除关联的容器绑定数据
        List<String> workCodes = works.stream().map(InStockWork::getCode).collect(Collectors.toList());
        List<Document> binds = mongoTemplate.find(Query.query(newCriteria().and("taskCode").in(workCodes)),
                Document.class, "entity_" + BIND_CONTAINER);
        Update delete = new Update();
        delete.set("isDelete", 1);
        binds.forEach(x -> updateById(x, delete, "entity_" + BIND_CONTAINER));
        // 删除所有的作业
        works.forEach(x -> updateById(x.get_id().toString(), delete, "entity_" + IN_STOCK_TASK));
        return "NULL";
    }

    @Transactional(rollbackFor = Exception.class)
    public void containerInStock(String taskCode, String method, String locCode, String podCode, Task task) {
        // 容器到达仓库
        // 一个单子可能需要多个容器来装
        List<String> operationNo = task.getOperationNo();
        Set<InStockWork> reportErpInOrderJobs = new LinkedHashSet<>();

        for (String no : operationNo) {
            InStockWork job = mongoTemplate.findOne(Query.query(newCriteria().and("code").is(no)), InStockWork.class, "entity_" + IN_STOCK_TASK);
            if (job != null) {
                String inOrderDtlId = job.getInOrderDtlId();
                String workCode = job.getCode();
                // 查询是否有其他还未入库成功的上架任务
                long count = mongoTemplate.count(Query.query(newCriteria().and("inOrderDtlId").is(inOrderDtlId)
                                .and("inStatus.id").ne(new ObjectId(inStockOK.getCode()))
                                .and("_id").ne(job.get_id())),
                        "entity_" + IN_STOCK_TASK);
                if (count == 0) {
                    reportErpInOrderJobs.add(job);
                }
                // 更新库存、库位绑定容器、解锁库位、解锁容器
                try {
                    boolean b = stockService.updateInStock(workCode);
                    log.info("任务到达仓库终点, 根据上架任务[{}]更新库存，更新结果[{}]", workCode, b);
                } catch (BusinessException e) {
                    log.error("任务到达仓库终点, 根据上架任务[{}]更新库存失败！", workCode, e);
                }

                // 如果是成品， 一个明细一个单据头，更新单据头状态
                if (PRODUCTION_IN_STOCK_DETAIL.equals(job.getDtlSourceTable())) {
                    Update update = new Update();
                    update.set("isStored", true);
                    mongoTemplate.updateFirst(Query.query(newCriteria().and("inOrder").is(job.getInOrder())), update,
                            "entity_" + PRODUCTION_IN_STOCK);
                }
            }
        }

        // 解锁容器
        Update update2 = new Update();
        update2.set("lock", false);
        mongoTemplate.updateFirst((Query.query(newCriteria().and("code").is(podCode))), update2, "entity_" + CONTAINER);
        // 库位与容器绑定并解锁库位
        Update update3 = new Update();
        update3.set("lock", false);
        update3.set("containerCode", podCode);
        mongoTemplate.updateFirst((Query.query(newCriteria().and("code").is(locCode))), update3, "entity_" + STOCK_LOCATION);

        // 入库暂存的数据返还
        List<Document> retainStocks = mongoTemplate.find(Query.query(newCriteria().and("containerCode").is(podCode)
                        .and("specialMark").is("入库锁定")), Document.class,
                "entity_" + STOCK_NAME);
        if (!CollectionUtils.isEmpty(retainStocks)) {
            for (Document stock : retainStocks) {
                Update update1 = new Update();
                update1.unset("specialMark");
                update1.set("locationCode", locCode);
                boolean succ = updateById(stock, update1, "entity_" + STOCK_NAME);
                log.info("恢复保留库存信息[{}]，修改内容:[{}], 结果:[{}]", stock, update1, succ);
            }
        }

        if (reportErpInOrderJobs.size() > 0) {
            // 上报 erp
            for (InStockWork work : reportErpInOrderJobs) {
                String dtlTableName = work.getDtlSourceTable();
                String dtlId = work.getInOrderDtlId();
                try {
                    boolean result = false;
                    if (RAW_MATERIAL_IN_STOCK_DETAIL.equals(dtlTableName)) {
                        result = rawMaterialInStockService.reportErp(dtlId);
                    } else if (PRODUCTION_IN_STOCK_DETAIL.equals(dtlTableName)) {
                        result = productInStockService.reportErp(dtlId);
                    } else if (SALES_RETURN_STOCK_DTL.equals(dtlTableName)) {
                        result = salesReturnStockService.reportErp(dtlId);
                    }
                    log.info("库存更新成功，上报erp系统，入库作业[{}]，结果: [{}]", work, result);
                } catch (Throwable throwable) {
                    log.error("入库库存更新成功，上报erp系统发生异常，入库作业[{}]", work, throwable);
                }
            }
        }
    }

    @Transactional(rollbackFor = Exception.class)
    public void containerArrive(String taskCode, String method, String locCode, String podCode, Task task) {
        List<InStockWork> inStockTasks = mongoTemplate.find(Query.query(newCriteria().and("code").in(task.getOperationNo())),
                InStockWork.class, "entity_" + IN_STOCK_TASK);
        String abc = null;
        if(!CollectionUtils.isEmpty(inStockTasks)) {
            abc = inStockTasks.get(0).getAbc();
        }
        // 容器到达分拣区
        Update update = new Update();
        // 下架到达分拣台入口的时候，不需要绑定库位与料箱
        if(A.equals(abc)) {
            update.set("containerCode", podCode);
        }
        update.set("lock", false);
        UpdateResult updateResult = mongoTemplate.updateFirst(Query.query(newCriteria().and("code").is(locCode)),
                update, "entity_" + STOCK_LOCATION);
        log.info("任务离开起点到达分拣区,更新库位[{}]绑定的容器[{}],更新结果[{}]", locCode, update, updateResult);
    }

    @Transactional(rollbackFor = Exception.class)
    public void handleOutBin(String taskCode, String method, String locCode, String podCode) {
        String containerCode = podCode;
        // 处理离开起点
        // 起点库位解绑容器，并解锁
        Update update = new Update();
        update.unset("containerCode");
        update.set("lock", false);
        UpdateResult updateResult = mongoTemplate.updateFirst(Query.query(newCriteria().and("code").is(locCode)), update, "entity_" + STOCK_LOCATION);
        log.info("任务离开起点节点回调,更新库位[{}]绑定的容器[{}],更新结果[{}]", locCode, update, updateResult);

        // 更新库存，将库存的库位置为空，保存绑定的容器
        List<Document> stocks = mongoTemplate.find(Query.query(newCriteria().and("containerCode").is(containerCode)
                        .and("locationCode").is(locCode)), Document.class,
                "entity_" + STOCK_NAME);
        if (!CollectionUtils.isEmpty(stocks)) {
            for (Document stock : stocks) {
                Update update1 = new Update();
                update1.unset("locationCode");
                update1.set("specialMark", "入库锁定");
                boolean succ = updateById(stock, update1, "entity_" + STOCK_NAME);
                log.info("保留库存信息[{}]，修改内容:[{}], 结果:[{}]", stock, update1, succ);
            }
        }
    }
}
